package me.kuye.jkuye.plugin.activerecord;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import me.kuye.jkuye.helper.DatabaseHelper;
import me.kuye.jkuye.util.StringUtil;

@SuppressWarnings({ "serial", "rawtypes" })
public abstract class Model<M extends Model> implements Serializable {
	private Map<String, Object> attrs = getAttrsMap();
	private Set<String> modifyFlag;
	private String configName = null;

	private Set<String> getModifyFlag() {
		if (modifyFlag == null) {
			Config config = getConfig();
			if (config == null) {
				modifyFlag = DatabaseHelper.brokenConfig.containerFactory.getModifyFlagSet();
			} else {
				modifyFlag = config.containerFactory.getModifyFlagSet();
			}
		}
		return modifyFlag;
	}

	public Model use(String configName) {
		if (StringUtil.isEmpty(configName)) {
			throw new IllegalArgumentException("the configName can not to be null");
		}
		this.configName = configName;
		return this;
	}

	private Class<? extends Model> getUsefulClass() {
		Class clazz = getClass();
		return clazz.getName().indexOf("EnhancerByCGLIB") == -1 ? clazz : clazz.getSuperclass();
	}

	protected Map<String, Object> getAttrs() {
		return attrs;
	}

	public M set(String attr, Object value) {
		if (getTable().hasColumnLabel(attr)) {
			attrs.put(attr, value);
			getModifyFlag().add(attr);
			return (M) this;
		}
		throw new ActiveRecordException(" the attribute can not exist " + attr);
	}

	public M put(String key, Object value) {
		attrs.put(key, value);
		return (M) this;
	}

	public M put(Map<String, Object> map) {
		attrs.putAll(map);
		return (M) this;
	}

	public M put(Model model) {
		attrs.putAll(model.getAttrs());
		return (M) this;
	}

	public M put(Record record) {
		attrs.putAll(record.getColumns());
		return (M) this;
	}

	public Record toRecord() {
		return new Record().setColumns(getAttrs());
	}

	public <T> T get(String key) {
		return (T) attrs.get(key);
	}

	public <T> T get(String key, Object defaultValue) {
		Object value = attrs.get(key);
		return (T) (value == null ? defaultValue : value);
	}

	public String getStr(String attr) {
		return (String) attrs.get(attr);
	}

	public Integer getInt(String attr) {
		return (Integer) attrs.get(attr);
	}

	public Long getLong(String attr) {
		return (Long) attrs.get(attr);
	}

	public java.math.BigInteger getBigInteger(String attr) {
		return (java.math.BigInteger) attrs.get(attr);
	}

	public java.util.Date getDate(String attr) {
		return (java.util.Date) attrs.get(attr);
	}

	public java.sql.Time getTime(String attr) {
		return (java.sql.Time) attrs.get(attr);
	}

	public java.sql.Timestamp getTimestamp(String attr) {
		return (java.sql.Timestamp) attrs.get(attr);
	}

	public Double getDouble(String attr) {
		return (Double) attrs.get(attr);
	}

	public Float getFloat(String attr) {
		return (Float) attrs.get(attr);
	}

	public Boolean getBoolean(String attr) {
		return (Boolean) attrs.get(attr);
	}

	public java.math.BigDecimal getBigDecimal(String attr) {
		return (java.math.BigDecimal) attrs.get(attr);
	}

	public byte[] getBytes(String attr) {
		return (byte[]) attrs.get(attr);
	}

	public Number getNumber(String attr) {
		return (Number) attrs.get(attr);
	}

	public Table getTable() {
		return TableMapping.getInstance().getTable(getUsefulClass());
	}

	private Map<String, Object> getAttrsMap() {
		Config config = getConfig();
		if (config == null) {
			return DatabaseHelper.brokenConfig.containerFactory.getAttrsMap();
		}
		return config.containerFactory.getAttrsMap();
	}

	private Config getConfig() {
		if (configName != null) {
			return DatabaseHelper.getConfig(configName);
		}
		return DatabaseHelper.getConfig(getUsefulClass());
	}

	public Page<M> paginate(int pageCount, int pageSize, String select, String sqlExceptSelect, Object... args) {
		Config config = getConfig();
		Connection connection = null;
		try {
			connection = config.getConnection();
			return pageinate(config, connection, pageCount, pageSize, select, sqlExceptSelect, args);
		} catch (Exception e) {
			throw new ActiveRecordException(e);
		} finally {
			config.colse(connection);
		}
	}

	@SuppressWarnings("unchecked")
	public Page<M> pageinate(Config config, Connection connection, int pageNow, int pageSize, String select,
			String sqlExceptSelect, Object... args)
					throws SQLException, InstantiationException, IllegalAccessException {
		if (pageNow < 1 || pageSize < 1) {
			throw new ActiveRecordException("pageNow and pageSize must be more than 0");
		}
		if (config.dialect.isTakeOverModelPaginate()) {
			try {
				return config.dialect.takeOverModelPaginate(connection, getUsefulClass(), pageNow, pageSize, select,
						sqlExceptSelect, args);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		long totalRecord = 0;
		int pageCount = 0;
		List result = Db.query(config, connection,
				" select count(*)" + DatabaseHelper.replaceFormatSqlOrderBy(sqlExceptSelect), args);
		int size = result.size();

		if (size == 1) {
			totalRecord = ((Number) result.get(0)).longValue();
		} else if (size > 1) {
			totalRecord = result.size();
		} else {
			return new Page<M>(0, 0, pageSize, pageNow, new ArrayList<M>(0));
		}
		pageCount = (int) (totalRecord / pageSize);
		if (totalRecord % pageSize != 0) {
			pageCount++;
		}
		if (pageNow > pageCount)
			return new Page<M>((int) totalRecord, pageCount, pageSize, pageNow, new ArrayList<M>(0));
		StringBuilder sql = new StringBuilder();
		config.dialect.forPaginate(sql, pageNow, pageSize, select, sqlExceptSelect);
		List<M> list = find(connection, sql.toString(), args);
		return new Page<M>((int) totalRecord, pageCount, pageSize, pageNow, list);
	}

	/**
	* @Description: 执行SQL语句并将resultSet转化为对应的实体类
	* @param connection 数据库连接
	* @param  sql 要执行的SQL语句
	* @param args SQL的参数
	* @throws SQLException
	* @throws InstantiationException
	* @throws IllegalAccessException  
	* @return List<M> 
	* @throws
	*/ 
	private List<M> find(Connection connection, String sql, Object[] args)
			throws SQLException, InstantiationException, IllegalAccessException {
		Config config = getConfig();
		Class<? extends Model> modelClass = getUsefulClass();
		if (config.devMode) {
			checkTableName(modelClass, sql);
		}
		PreparedStatement preparedStatement = connection.prepareStatement(sql);
		config.dialect.fillStatement(preparedStatement, args);
		ResultSet result = preparedStatement.executeQuery();
		List<M> modelList = ModelBuilder.build(result, modelClass);
		DatabaseHelper.closeQuietly(result, preparedStatement);
		return modelList;
	}

	/**
	* @Description: 检查tableName是否存在于sql语句中
	* @param  modelClass 实体类
	* @param  sql  SQL语句
	* @return void 
	* @throws
	*/ 
	private void checkTableName(Class<? extends Model> modelClass, String sql) {
		Table table = TableMapping.getInstance().getTable(modelClass);
		if (sql.toLowerCase().contains(table.getName().toLowerCase())) {
			throw new ActiveRecordException(" the table name" + table.getName() + " is not exist in the your sql");
		}
	}

}
